// (c) Ivan Gagis 2008
// e-mail: igagis@gmail.com
// Version: 0.1

// Description:
//          cross platfrom C++ Sockets wrapper
//
//      Some code was taken from SDL_net library (http://libsdl.org),
//      so, this wrapper inherits the LGPL license from SDL_net.
//

#include "sckt.hpp"

#include <string.h>

#ifdef DEBUG
#include <iostream>
#define M_SCKT_ASSERT(x) if(!(x)){ \
                             std::cout << "[sckt] !!! Assertion failed at "<<__FILE__<<":"<<__LINE__<<std::endl; \
                             throw sckt::C_Exc("skct:: assertion failed !!!"); \
                         }
#else
#define M_SCKT_ASSERT(x)
#endif


//system specific defines, typedefs and includes
#ifdef __WIN32__

#include <windows.h>
typedef SOCKET T_Socket;
#define M_INVALID_SOCKET INVALID_SOCKET
#define M_SOCKET_ERROR SOCKET_ERROR
#define M_EINTR WSAEINTR

#else //assume linux/unix

#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
typedef int T_Socket;
#define M_INVALID_SOCKET (-1)
#define M_SOCKET_ERROR (-1)
#define M_EINTR EINTR

#endif

using namespace sckt;

C_Sockets* C_Sockets::instance = 0;

inline static T_Socket& CastToSocket(sckt::C_Socket::C_SystemIndependentSocketHandle& s){
    M_SCKT_ASSERT(sizeof(s)>=sizeof(T_Socket)) //TODO: consider static assert here
    return *reinterpret_cast<T_Socket*>(&s);
};

inline static const T_Socket& CastToSocket(const sckt::C_Socket::C_SystemIndependentSocketHandle& s){
    M_SCKT_ASSERT(sizeof(s)>=sizeof(T_Socket)) //TODO: consider static assert here
    return *reinterpret_cast<const T_Socket*>(&s);
};

//static
void C_Sockets::InitSockets(){
#ifdef __WIN32__
    WORD versionWanted = MAKEWORD(1,1);
    WSADATA wsaData;
    if(WSAStartup(versionWanted, &wsaData) != 0 )
        throw sckt::C_Exc("sdlw::InitSockets(): Winsock 1.1 initialization failed");
#else //assume linux/unix
    // SIGPIPE is generated when a remote socket is closed
    void (*handler)(int);
    handler = signal(SIGPIPE, SIG_IGN);
    if(handler != SIG_DFL)
        signal(SIGPIPE, handler);
#endif
};

//static
void C_Sockets::DeinitSockets(){
#ifdef __WIN32__
    // Clean up windows networking
    if(WSACleanup() == M_SOCKET_ERROR)
        if(WSAGetLastError() == WSAEINPROGRESS){
            WSACancelBlockingCall();
            WSACleanup();
        }
#else //assume linux/unix
    // Restore the SIGPIPE handler
    void (*handler)(int);
    handler = signal(SIGPIPE, SIG_DFL);
    if(handler != SIG_IGN)
        signal(SIGPIPE, handler);
#endif
};

sckt::C_Exc::C_Exc(const char* message){
    if(message==0)
        message = "unknown exception";
    
    int len = strlen(message);
    this->msg = new char[len+1];
    memcpy(this->msg, message, len);
    this->msg[len] = 0;//null-terminate
};

sckt::C_Exc::~C_Exc()throw(){
    delete[] this->msg;
};

C_Sockets::C_Sockets(){
    if(C_Sockets::instance != 0)
        throw sckt::C_Exc("C_Sockets::C_Sockets(): C_Sockets singletone object is already created");
    C_Sockets::InitSockets();
    this->instance = this;
};

C_Sockets::~C_Sockets(){
    M_SCKT_ASSERT(this->instance)
    C_Sockets::DeinitSockets();
    this->instance = 0;
};

C_Socket::C_Socket() :
        isReady(false)
{
    CastToSocket(this->socket) = M_INVALID_SOCKET;
};

bool C_Socket::IsValid()const{
    return CastToSocket(this->socket) != M_INVALID_SOCKET;
};

C_IPAddress::C_IPAddress(u16 p) :
        host(INADDR_ANY),
        port(p)
{};

C_TCPSocket& C_TCPSocket::operator=(const C_TCPSocket& s){//same as std::auto_ptr
    this->~C_TCPSocket();
    CastToSocket(this->socket) = CastToSocket(s.socket);
    this->isReady = s.isReady;
    this->isServer = s.isServer;
    CastToSocket( const_cast<C_TCPSocket&>(s).socket ) = M_INVALID_SOCKET;//same as std::auto_ptr
    return *this;
};

/* Open a TCP network socket
   If 'remote' is NULL, this creates a local server socket on the given port,
   otherwise a TCP connection to the remote host and port is attempted.
   The newly created socket is returned, or NULL if there was an error.
*/
void C_TCPSocket::Open(const C_IPAddress& ip){
    M_SCKT_ASSERT(!this->IsValid())
    
    CastToSocket(this->socket) = ::socket(AF_INET, SOCK_STREAM, 0);
    if(CastToSocket(this->socket) == M_INVALID_SOCKET)
        throw sckt::C_Exc("C_TCPSocket::Open(): Couldn't create socket");
    
    // Connect to remote, or bind locally, as appropriate
    if( (ip.host != INADDR_NONE) && (ip.host != INADDR_ANY) ){//Connecting to remote
        sockaddr_in sockAddr;
        memset(&sockAddr, 0, sizeof(sockAddr));
        sockAddr.sin_family = AF_INET;
        sockAddr.sin_addr.s_addr = ip.host;
        sockAddr.sin_port = ip.port;
        
        // Connect to the remote host
        if( connect(CastToSocket(this->socket), reinterpret_cast<sockaddr *>(&sockAddr), sizeof(sockAddr)) == M_SOCKET_ERROR )
            throw sckt::C_Exc("C_TCPSocket::Open(): Couldn't connect to remote host");
        
        this->isServer = false;
    }else{//Binding locally
        sockaddr_in sockAddr;
        memset(&sockAddr, 0, sizeof(sockAddr));
		sockAddr.sin_family = AF_INET;
		sockAddr.sin_addr.s_addr = INADDR_ANY;
		sockAddr.sin_port = ip.port;
        
        /*
         * Windows gets bad mojo with SO_REUSEADDR:
         * http://www.devolution.com/pipermail/sdl/2005-September/070491.html
         *   --ryan.
         */
#ifndef WIN32
		/* allow local address reuse */
		{ int yes = 1;
			setsockopt(CastToSocket(this->socket), SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
		}
#endif
        // Bind the socket for listening
        if( bind(CastToSocket(this->socket), reinterpret_cast<sockaddr*>(&sockAddr), sizeof(sockAddr)) == M_SOCKET_ERROR )
            throw sckt::C_Exc("C_TCPSocket::Open(): Couldn't bind to local port");
        
        if( listen(CastToSocket(this->socket), 5) == M_SOCKET_ERROR )
            throw sckt::C_Exc("C_TCPSocket::Open(): Couldn't listen to local port");
        
        //Set the socket to non-blocking mode for accept()
#if defined(__BEOS__) && defined(SO_NONBLOCK)
        // On BeOS r5 there is O_NONBLOCK but it's for files only
        {
            long b = 1;
            setsockopt(CastToSocket(this->socket), SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));
        }
#elif defined(O_NONBLOCK)
        {
            fcntl(CastToSocket(this->socket), F_SETFL, O_NONBLOCK);
        }
#elif defined(WIN32)
        {
            u_long mode = 1;
            ioctlsocket(CastToSocket(this->socket), FIONBIO, &mode);
        }
#elif defined(__OS2__)
        {
            int dontblock = 1;
            ioctl(CastToSocket(this->socket), FIONBIO, &dontblock);
        }
#else
#warning How do we set non-blocking mode on other operating systems?
#endif
        this->isServer = true;//indicate that this is a server socket and it can do Accept()
    }//~Binding locally
    this->isReady = false;
};


void C_TCPSocket::Close(){
    if(this->IsValid()){
#ifdef __WIN32__
        closesocket(CastToSocket(this->socket));
#else //assume linux/unix
        close(CastToSocket(this->socket));
#endif
    }
    CastToSocket(this->socket) = M_INVALID_SOCKET;
};


C_TCPSocket C_TCPSocket::Accept(){
    M_SCKT_ASSERT(this->IsValid())
    M_SCKT_ASSERT(this->isServer)
    if(!this->isServer)
        throw sckt::C_Exc("C_TCPSocket::Accept(): only server sockets can do Accept()");
    
    this->isReady = false;
    
    sockaddr_in sockAddr;
    
#ifdef __WIN32__
    int sock_alen = sizeof(sockAddr);
#else //linux/unix
    socklen_t sock_alen = sizeof(sockAddr);
#endif
    
    C_TCPSocket sock;//allocate a new socket object
    
    CastToSocket(sock.socket) = accept(CastToSocket(this->socket), reinterpret_cast<sockaddr*>(&sockAddr),
#ifdef USE_GUSI_SOCKETS
                (unsigned int *)&sock_alen);
#else
                &sock_alen);
#endif
    
    if(CastToSocket(sock.socket) == M_INVALID_SOCKET)
        return sock;//no connections to be accepted, return invalid socket
    
    //set blocking mode
#ifdef __WIN32__
    {
        /* passing a zero value, socket mode set to block on */
        u_long mode = 0;
        ioctlsocket(CastToSocket(sock.socket), FIONBIO, &mode);
    }
#elif defined(O_NONBLOCK)
    {
        int flags = fcntl(CastToSocket(sock.socket), F_GETFL, 0);
        fcntl(CastToSocket(sock.socket), F_SETFL, flags & ~O_NONBLOCK);
    }
#else
#error do not know how to set blocking mode to socket
#endif //#ifdef WIN32
    return sock;//return a newly created socket
};


sckt::uint C_TCPSocket::Send(const byte* data, uint size){
    M_SCKT_ASSERT(this->IsValid())
    M_SCKT_ASSERT(!this->isServer)
    M_SCKT_ASSERT(data && size != 0 && size < 0xFFFFFF) //limit on size is 16.7 millions
    
    if(this->isServer)
        throw sckt::C_Exc("C_TCPSocket::Send(): this is a server socket, cannot do Send()");
    
    int sent = 0,
        left = int(size);
    
    //Keep sending data until it's sent or an error occurs
    int errorCode = 0;
    M_SCKT_ASSERT(M_EINTR != 0)//make sure errorCode=0 means 'no M_EINTR error'

    int res;
    do{
        res = send(CastToSocket(this->socket), reinterpret_cast<const char*>(data), left, 0);
        if(res == M_SOCKET_ERROR){
#ifdef __WIN32__
            errorCode = WSAGetLastError();
#else //linux/unix
            errorCode = errno;
#endif
        }else{
            M_SCKT_ASSERT(res > 0)
            sent += res;
            left -= res;
            data += res;
        }
    }while( (left > 0) && ((res != M_SOCKET_ERROR) || (errno == M_EINTR)) );

	return uint(sent);
};


sckt::uint C_TCPSocket::Recv(byte* buf, uint maxSize){
    M_SCKT_ASSERT(this->IsValid())
    M_SCKT_ASSERT(!this->isServer)
    M_SCKT_ASSERT(buf && maxSize !=0 && maxSize < 0xffffff)
    
    // Server sockets are for accepting connections only
    if(this->isServer)
        throw sckt::C_Exc("C_TCPSocket::Recv(): this is a server socket, cannot do Recv()");

    int len;
    int errorCode = 0;
    M_SCKT_ASSERT(M_EINTR != 0)//make sure errorCode=0 means 'no M_EINTR error'
    do{
        len = recv(CastToSocket(this->socket), reinterpret_cast<char *>(buf), maxSize, 0);
        if(len == M_SOCKET_ERROR){
#ifdef __WIN32__
            errorCode = WSAGetLastError();
#else //linux/unix
            errorCode = errno;
#endif
        }
    }while(errorCode == M_EINTR);
    
    if(len == M_SOCKET_ERROR)
        throw sckt::C_Exc("C_TCPSocket::Recv(): recv() failed");
    
    this->isReady = false;
	return uint(len);
};



void C_SocketSet::AddSocket(C_Socket *sock){
    if(!sock)
        throw C_Exc("C_SocketSet::AddSocket(): null socket pointer passed as argument");
    
    M_SCKT_ASSERT(this->numSockets<=this->maxSockets)
    
    if(this->numSockets == this->maxSockets)
        throw sckt::C_Exc("C_SocketSet::AddSocket(): socket set is full");
    
    for(uint i=0; i<this->numSockets; ++i){
        if(this->set[i] == sock)
            return;
    }
    
    this->set[this->numSockets] = sock;
    ++this->numSockets;
};

void C_SocketSet::RemoveSocket(C_Socket *sock){
    if(!sock)
        throw C_Exc("C_SocketSet::RemoveSocket(): null socket pointer passed as argument");
    
    uint i;
    for(i=0; i<this->numSockets; ++i)
        if(this->set[i]==sock)
            break;
    
    if(i==this->numSockets)
        return;//socket sock not found in the set

    --this->numSockets;//decrease numsockets before shifting the sockets
    //shift sockets
    for(;i<this->numSockets; ++i){
        this->set[i] = this->set[i+1];
    }
};

bool C_SocketSet::CheckSockets(uint timeoutMillis){
    T_Socket maxfd = 0;
    
    //Find the largest file descriptor
    for(uint i = 0; i<this->numSockets; ++i){
        M_SCKT_ASSERT(this->set[i])
        if(CastToSocket(this->set[i]->socket) > maxfd)
            maxfd = CastToSocket(this->set[i]->socket);
    }
    
    int retval;
    fd_set readMask;
    
    //Check the file descriptors for available data
    int errorCode = 0;
    M_SCKT_ASSERT(M_EINTR != 0)//make sure errorCode=0 means 'no M_EINTR error'
    do{
        //Set up the mask of file descriptors
        FD_ZERO(&readMask);
        for(uint i=0; i<this->numSockets; ++i){
            T_Socket socketHnd = CastToSocket(this->set[i]->socket);
            FD_SET(socketHnd, &readMask);
        }
        
        // Set up the timeout
        //TODO: consider moving this out of do{}while() loop
        timeval tv;
        tv.tv_sec = timeoutMillis/1000;
        tv.tv_usec = (timeoutMillis%1000)*1000;
        
        retval = select(maxfd+1, &readMask, NULL, NULL, &tv);
        if(retval == M_SOCKET_ERROR){
#ifdef __WIN32__
            errorCode = WSAGetLastError();
#else
            errorCode = errno;
#endif
        }
    }while(errorCode == M_EINTR);
    
    // Mark all file descriptors ready that have data available
    if(retval != 0 && retval != M_SOCKET_ERROR){
        int numSocketsReady = 0;
        for(uint i=0; i<this->numSockets; ++i){
            T_Socket socketHnd = CastToSocket(this->set[i]->socket);
            if( (FD_ISSET(socketHnd, &readMask)) ){
                this->set[i]->isReady = true;
                ++numSocketsReady;
            }
        }
        
        //on Win32 when compiling with mingw there are some strange things,
        //sometimes retval is not zero but there is no any sockets marked as ready in readMask.
        //I do not know why this happens on win32 and mingw. The workaround is to calculate number
        //of active sockets mnually, ignoring the retval value.
        if(numSocketsReady > 0)
            return true;
    }
    return false;
};
